"""
api.dao package
- dao_engine: Engine/Session/Base/init_db
- model: one-file-per-entity (Flow, Run)
- schema: ORM -> dict conversions
- mapper: table-level CRUD using dao_engine

This package also exposes convenience functions for common CRUD operations.
"""
from __future__ import annotations

from typing import Any, Dict, List, Optional

from .dao_engine import Base, get_engine, get_session, init_db
from .model.flow_model import Flow
from .model.run_model import Run
from .schema.flow_schema import flow_to_dict
from .schema.run_schema import run_to_dict
from .mapper.flow_mapper import FlowMapper
from .mapper.run_mapper import RunMapper

__all__ = [
    "Base",
    "get_engine",
    "get_session",
    "init_db",
    "Flow",
    "Run",
    "flow_to_dict",
    "run_to_dict",
    "FlowMapper",
    "RunMapper",
    # convenience functions
    "create_flow",
    "update_flow",
    "get_flow",
    "list_flows",
    "delete_flow",
    "create_run",
    "update_run",
    "get_run",
    "list_runs",
    "delete_run",
]


# Convenience wrappers that open/close session automatically and return dicts

def create_flow(name: str, description: Optional[str] = None, flow_json: Optional[Any] = None) -> Dict[str, Any]:
    # validate name to avoid DB NOT NULL errors
    if not name or str(name).strip() == "":
        raise ValueError("Flow name is required")
    s = get_session()
    try:
        m = FlowMapper(s)
        f = m.create_flow(name=name, description=description, flow_json=flow_json)
        return flow_to_dict(f)
    finally:
        s.close()


def update_flow(flow_id: int, **kwargs) -> Optional[Dict[str, Any]]:
    # sanitize/validate name if present
    if "name" in kwargs:
        n = kwargs.get("name")
        if n is None:
            # do not update name when None is provided
            kwargs.pop("name")
        elif str(n).strip() == "":
            raise ValueError("Flow name cannot be empty")
    # avoid updating flow_json to NULL when not provided
    if "flow_json" in kwargs and kwargs.get("flow_json") is None:
        kwargs.pop("flow_json")
    s = get_session()
    try:
        m = FlowMapper(s)
        f = m.update_flow(flow_id, **kwargs)
        return flow_to_dict(f) if f else None
    finally:
        s.close()


def get_flow(flow_id: int) -> Optional[Dict[str, Any]]:
    s = get_session()
    try:
        m = FlowMapper(s)
        f = m.get_flow(flow_id)
        return flow_to_dict(f) if f else None
    finally:
        s.close()


def list_flows(limit: int = 50, offset: int = 0) -> List[Dict[str, Any]]:
    s = get_session()
    try:
        m = FlowMapper(s)
        fs = m.list_flows(limit=limit, offset=offset)
        return [flow_to_dict(f) for f in fs]
    finally:
        s.close()


def delete_flow(flow_id: int) -> bool:
    s = get_session()
    try:
        m = FlowMapper(s)
        return m.delete_flow(flow_id)
    finally:
        s.close()


def create_run(flow_id: int, status: str = "pending", **kwargs) -> Dict[str, Any]:
    s = get_session()
    try:
        m = RunMapper(s)
        r = m.create_run(flow_id=flow_id, status=status, **kwargs)
        return run_to_dict(r)
    finally:
        s.close()


def update_run(run_id: int, **kwargs) -> Optional[Dict[str, Any]]:
    s = get_session()
    try:
        m = RunMapper(s)
        r = m.update_run(run_id, **kwargs)
        return run_to_dict(r) if r else None
    finally:
        s.close()


def get_run(run_id: int) -> Optional[Dict[str, Any]]:
    s = get_session()
    try:
        m = RunMapper(s)
        r = m.get_run(run_id)
        return run_to_dict(r) if r else None
    finally:
        s.close()


def list_runs(limit: int = 50, offset: int = 0) -> List[Dict[str, Any]]:
    s = get_session()
    try:
        m = RunMapper(s)
        rs = m.list_runs(limit=limit, offset=offset)
        return [run_to_dict(r) for r in rs]
    finally:
        s.close()


def delete_run(run_id: int) -> bool:
    s = get_session()
    try:
        m = RunMapper(s)
        return m.delete_run(run_id)
    finally:
        s.close()